package tests.model_tests;

import static org.junit.Assert.*;

import org.junit.*;

import application.models.collections.CustomerCollection;
import application.models.Observer;
import application.models.Observable;
import application.models.datalayer.DataCondition;
import application.models.datalayer.Attribute;
import application.models.datalayer.Operator;
import application.models.datalayer.Date;
import application.models.models.CustomerModel;
import application.models.exceptions.InvalidAttributeException;
import application.models.exceptions.MissingDataException;

import java.io.IOException;

/**
 *	This class tests the interface of the CustomerCollection class. Detailed comments are included in the test methods
 *
 *	@author Morten Therkildsen & Philip Rasmussen
 */
public class CustomerCollection_test {

	private boolean observerFail;
	private boolean observer2Fail;

	private CustomerCollection col;
	
	@Before
	public void setupCollection(){
		
		col = new CustomerCollection();
	}
	
	@Test
	public void constructorTest() {
		
		new CustomerCollection();
	}
	
	@Test
	public void loadAndSizeTest() throws IOException, InvalidAttributeException, MissingDataException {
		
		// Test that empty collections has a size of 0
		if(col.size() != 0) fail();
		
		///////////////
		// NEXT TEST //
		///////////////
		
		// Create a new model for the test
		CustomerModel model = new CustomerModel();
		
		// Set attributes
		for(Attribute attribute : model){
			
			Object value = null;

			if(attribute.getDataType().equalsIgnoreCase("TEXT")) value = "Test!";
			else if(attribute.getDataType().equalsIgnoreCase("INTEGER")) value = new Integer(0);
			else if(attribute.getDataType().equalsIgnoreCase("DATE")) value = new Date(0);
			else if(attribute.getDataType().equalsIgnoreCase("BOOLEAN")) value = new Boolean(true);
			
			model.setAttribute(attribute.getName(), value);
		}
		
		// Soon ready for the test...
		model.save();
		
		col.load(); // Load all models
		
		// Test that we at least has loaded 1 model
		if(col.size() == 0) fail();
		
		model.delete(); // Cleanup
	}
	
	@Test
	public void observerTest() throws InvalidAttributeException, IOException {
		
		// New car collection with filters that matches nothing to prevent any loading from happening
		col.addFilter(new DataCondition(CustomerModel.getMainSetIdAttribute(), Operator.EQUALS, new Integer(0)));
		
		Observer observer = new Observer() {
			
			public void notify(Observable subject){
				
				if(subject != col) fail("observer received a wrong subject"); // If the subject is not the collection it happens on
				
				observerFail = false;
			}
		};
		
		// Test that we may add and remove an observer
		observerFail = true;
		col.addObserver(observer);
		col.load(); // observer should make observerFail = false
		if(observerFail) fail();
		
		observerFail = true;
		col.removeObserver(observer);
		col.load();
		if(!observerFail) fail(); // If observerFail is false, the observer has not been removed
		
		// Test that we may add and remove multiple observers
		Observer observer2 = new Observer() {
			
			public void notify(Observable subject){
				
				if(subject != col) fail("observer received a wrong subject"); // If the subject is not the collection it happens on
				
				observer2Fail = false;
			}
		};
		
		// Add 2 observers and test
		observerFail = true;
		observer2Fail = true;
		col.addObserver(observer);
		col.addObserver(observer2);
		col.load();
		if(observerFail || observer2Fail) fail();
		
		// Remove one - observer must fail and observer2 succeed or both have been removed
		observerFail = true;
		observer2Fail = true;
		col.removeObserver(observer);
		col.load();
		if(!observerFail || observer2Fail) fail();
		
		// Remove the remaining observer
		observerFail = true;
		observer2Fail = true;
		col.removeObserver(observer2);
		col.load();
		if(!observerFail || !observer2Fail) fail();
		
		// Test that you cannot add null observers
		boolean fail = true;
		try{ col.addObserver(null); }
		catch(NullPointerException e){fail = false;}
		if(fail) fail();
	}
	
	@Test
	public void iteratorTest() throws IOException {
		
		// Test that it returns an iterator
		if(col.iterator() == null) fail();
		
		// Test that the iterator gives access to CustomerModels when the collection is loaded
		col.load(); // All cars
		
		boolean failure = false;
		for(CustomerModel model : col){
			
			if(model == null) failure = true;
		}
		if(failure) fail();
	}
	
	@Test
	public void filterTest() throws InvalidAttributeException, IOException, MissingDataException {
		
		// Create a new model for the test
		CustomerModel model = new CustomerModel();
		
		// Set attributes
		for(Attribute attribute : model){
			
			Object value = null;

			if(attribute.getDataType().equalsIgnoreCase("TEXT")) value = "Test!";
			else if(attribute.getDataType().equalsIgnoreCase("INTEGER")) value = new Integer(0);
			else if(attribute.getDataType().equalsIgnoreCase("DATE")) value = new Date(0);
			else if(attribute.getDataType().equalsIgnoreCase("BOOLEAN")) value = new Boolean(true);
			
			model.setAttribute(attribute.getName(), value);
		}
		
		// Soon ready for the test...
		model.save();
		
		// BEGIN TESTING
		Integer id1 = new Integer(model.getId());
		DataCondition idFilter1 = new DataCondition(CustomerModel.getMainSetIdAttribute(), Operator.EQUALS, id1);
		DataCondition idFilter2 = new DataCondition(CustomerModel.getMainSetIdAttribute(), Operator.EQUALS, new Integer(2));
		DataCondition idFilter2_copy = new DataCondition(CustomerModel.getMainSetIdAttribute(), Operator.EQUALS, new Integer(2));
		
		// Test that the list of filters is empty
		assertTrue(col.getFilters().length == 0);
		
		// Test we may add filters
		col.addFilter(idFilter1);
		col.addFilter(idFilter2);
		
		// Test that a filter concerning a non existant attribute throws an exception
		try{
			col.addFilter(new DataCondition("test_attribute", Operator.EQUALS));
		}catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		}
		
		// Test that a null value throws an exception
		try{
			col.addFilter(null);
		}catch(NullPointerException e){ assertTrue(e instanceof NullPointerException); // This is excepted
		}
		
		// Test that we may get all filters
		DataCondition[] filters = col.getFilters();
		int count = 0;
		for(DataCondition filter : filters){
			
			if(!(filter.equals(idFilter1) || filter.equals(idFilter2))) fail("Collection had filters set that should not be present.");
			count++;
		}
		if(count != 2) fail("getFilters() returned more or less filters than those set.");
		
		// Test that duplicate filters are not allowed
		col.addFilter(idFilter1);
		if(filters.length != col.getFilters().length) fail("Allowed addition of duplicate filter");
		
		// Test that we may remove filters one by one
		col.removeFilter(idFilter2);
		DataCondition[] filters2 = col.getFilters();
		if(filters2.length != 1 || !filters2[0].equals(idFilter1)) fail();
		
		// Test that we may remove filters one by one by using different but equal objects
		col.addFilter(idFilter2_copy);
		col.removeFilter(idFilter2_copy);
		filters2 = col.getFilters();
		if(filters2.length != 1 || !filters2[0].equals(idFilter1)) fail();
		
		// Test that nothing happens when trying to remove a filter that doesn't exist
		try{
			col.removeFilter(new DataCondition("test", Operator.LESS));
		}catch(Exception e){ fail("An Exception was thrown");
		}
		
		// Test that nothing happens when trying to remove null
		try{
			col.removeFilter(null);
		}catch(Exception e){ fail("An Exception was thrown");
		}
		
		// Test that applied filters are empty when the filters have not been applied
		if(col.getAppliedFilters().length != 0) fail();
		
		// Test that applied filters match the set filters after load
		col.load();
		DataCondition[] appliedFilters = col.getAppliedFilters();
		if(appliedFilters.length != filters2.length || !appliedFilters[0].equals(filters2[0])) fail();
		
		// Test that filters works - only 1 model should be in the collection based on the applied filter
		for(CustomerModel loadedModel : col){
			
			if(loadedModel.getId() != id1.intValue()) fail();
		}
		
		// Test that applied filters are not changed with set filters
		col.addFilter(idFilter2);
		if(col.getAppliedFilters().length != appliedFilters.length) fail();
		
		// Test that applied filters works when models are updated (live collections)
		model.delete(); // Delete test model
		if(col.iterator().hasNext()) fail();
	}
}
